/*
 * Copyright (c) 2001-2007, Inversoft, All Rights Reserved
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific
 * language governing permissions and limitations under the License.
 */
package org.inversoft.vertigo.migration;

import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.sql.Connection;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.logging.Logger;

import net.java.util.Version;

/**
 * <p>
 * This class is used to apply a set of SQL files to a database.
 * This requires that the database connection has already been established
 * and that the database is correctly setup to handle the SQL statements
 * from the files.
 * </p>
 *
 * <p>
 * SQL files are located on the file system in a specific directory.
 * Each file within that directory might be executed, depending on how
 * this patcher is constructed.
 * </p>
 *
 * <p>
 * This class uses the version number given and executes only the SQL
 * files that contain that version or higher in the name of the file.
 * This allows a production or staging database to be patched by only
 * executing the files that are needed to incrementally update the data
 * or tables. This also is nice because it allows development to be tested
 * to ensure that the SQL scripts work. It can create the database from
 * scratch by running all the SQL files.
 * </p>
 *
 * <p>
 * SQL files must have a specific naming scheme in order to determine
 * what their versions are. The name must be in this form:
 * </p>
 *
 * <pre>
 * version-name.sql
 * </pre>
 *
 * <p>
 * For example, <code>14-update-user-table.sql</code> could be a run
 * file. The version must be an integer value that is parsable be the
 * Integer class. This version will determine what the version of
 * the database is after the patcher has run all of the run files as
 * well as whice run files to run and the order to run them in.
 * </p>
 *
 * @author  Brian Pontarelli
 */
public class MultipleScriptRunner {
    private static final Logger logger = Logger.getLogger(MultipleScriptRunner.class.getName());
    private List<URL> urls = new ArrayList<URL>();
    private Version version;
    private ScriptExecutor executor;

    /**
     * Creates a new database script runner that will use the given connection, SQL directory and
     * version number.
     *
     * @param   connection The JDBC connection to use when patching.
     * @param   sqlDir The directory on the file system that contains the run files.
     * @param   version The version that controls which run files to execute.
     * @throws  IllegalArgumentException If the run SQL directory is not valid.
     */
    public MultipleScriptRunner(Connection connection, File sqlDir, Version version) {
        if (sqlDir.isFile() || !sqlDir.exists()) {
            throw new IllegalArgumentException("Invalid SQL directory [" +
                sqlDir.getAbsolutePath() + "]. The directory contain the SQL files.");
        }

        File[] files = sqlDir.listFiles(new FilenameFilter() {
            public boolean accept(File dir, String name) {
                return name.endsWith(".sql");
            }
        });

        for (File file : files) {
            try {
                urls.add(file.toURI().toURL());
            } catch (MalformedURLException e) {
                throw new IllegalArgumentException(e);
            }
        }

        this.version = version;
        this.executor = new ScriptExecutor(connection);
    }

    /**
     * Creates a new database script runner that will use the given connection, URLs and
     * version number.
     *
     * @param   connection The JDBC connection to use when patching.
     * @param   urls The URLs that might be run, depending on the version.
     * @param   version The version that controls which run files to execute.
     * @throws  IllegalArgumentException If the run SQL directory is not valid.
     */
    public MultipleScriptRunner(Connection connection, List<URL> urls, Version version) {
        this.urls.addAll(urls);
        this.version = version;
        this.executor = new ScriptExecutor(connection);
    }

    /**
     * Runs the scripts using the method described in the class comment.
     *
     * @return  Returns the last version that is run.
     * @throws  IOException If anything goes wrong while patching.
     */
    public Version run() throws IOException {
        int length = urls.size();
        SortedSet<Version> versions = new TreeSet<Version>();
        Map<Version, URL> map = new HashMap<Version, URL>(length);
        for (URL url : urls) {
            String urlStr = url.toString();
            int index = urlStr.lastIndexOf("/");
            String numberStr = urlStr.substring(index + 1).split("-", 2)[0];
            Version version = new Version(numberStr);
            if (version.compareTo(this.version) >= 0) {
                versions.add(version);
                map.put(version, url);
            }
        }

        // Iterate over the versions in order
        for (Version v : versions) {
            executor.execute(map.get(v));
        }

        if (versions.isEmpty()) {
            return null;
        }

        return versions.last();
    }
}